# TODO: Fix engine and its wx canvas so that the size of the engine determines the size
#       of the wx window and not vice versa.
from __future__ import print_function
from __future__ import absolute_import

import stackless
import sys
import logging
import time
import os
import io
import weakref
import uuid
#import cPickle as cpickle
import pickle as cpickle # for debugging
import cStringIO as cstringio
import copy_reg
import zlib
import zipfile
import struct
import thread
from xml.dom import minidom
from collections import OrderedDict

from kharlia.__engine import *
from kharlia._engine import *

from kharlia import core, wxinter
from kharlia.core import NativeClass

# Actor replication info packet type
PT_ACTOR_REP = 10
# Highest allowed index for Python rep vars
REP_MAX_INDEX = 0xEF
# Index used for pickled native replication info
REP_NATIVE_INDEX = 0xF0

# kharlia.engine logger
GLogger = None
# replication specific logger
GRepLogger = None
# Ordered dict of AComponent classes keyed by name
GComponentClasses = OrderedDict()
# List of classes with netvars
GNetEnabledClasses = []
# True if comp classes have had special vars initialized
GComponentClassesReadied = False
# State for current level unpickling operation
GUnpicklingLevel = None
GUnpicklingLevelName = None
GUnpicklingWorld = None
GUnpicklingActor = None
GUnpicklingActorName = None
# dict of actor pickling operations
GActorPickleState = None

GTest0 = None

def InitComponentClasses():
    '''
    This function sets up certain variables for component classes needed to
    implement some other features.

    Class variables set by this function:
      _ClassNetVars - Python net variables in this class
      _AllNetVars - Python net variable of this and all base classes
      _RepListClassIndices - Indices of classes in the MRO that define GetRepList()
    '''
    global GComponentClassesReadied
    if GComponentClassesReadied:
        return
    GLogger.debug('initing actor classes...')
    VerifyComponentClasses()
    counter = 1
    for cls in GComponentClasses.itervalues():
        GLogger.debug('initing actor class %s', cls.__name__)
        # Build net var list
        clsnetvars = []
        allnetvars = []
        clsdict = cls.__dict__

        netvars = clsdict.get('__repvars__', None)
        if netvars is not None and len(netvars) > 0:
            clsnetvars.extend(netvars)
            cls.__repindex__ = counter
            counter += 1

        # We'll extend allnetvars with this in the MRO loop
        cls.__classrepvars__ = tuple(clsnetvars)

        # Build list of all net vars from super classes
        for i in reversed(cls.mro()):
            id = i.__dict__
            v = id.get('__classrepvars__', None)
            if v is not None:
                allnetvars.extend(v)

        if len(clsnetvars) > 0:
            assert clsnetvars[-1] == allnetvars[-1]

        # List of net vars for this class and its bases
        cls.__allrepvars__ = tuple(allnetvars)

        # Check for classes that have GetRepList defined
        grlclasses = []
        for i, c in enumerate(cls.mro()):
            if '__getreplist__' in c.__dict__:
                grlclasses.append(i)
        cls.__repclassindices__ = tuple(grlclasses)

    GComponentClassesReadied = True
    GLogger.debug('finished initing actor classes')

def VerifyComponentClasses():
    for cls in GComponentClasses.itervalues():
        if not issubclass(cls, AComponent):
            raise TypeError('registered component class \'{0}\' is ' \
                            'not a subclass of AComponent'.format(i))

def CompileODS(path, savecompiled=True):
    '''
    Compiles an OpenDocument Spreadsheet
    '''
    raise NotImplementedError()

def _ParseTMX(doc):
# Verify that we have a map
    el_doc = doc.documentElement
    if el_doc.tagName != u'map':
        raise RuntimeError('missing map element')
    GLogger.debug('confirmed map element')

    # Verify that the map is of a valid size
    levelwidth_tiles = int(el_doc.getAttribute(u'width'))
    levelheight_tiles = int(el_doc.getAttribute(u'height'))
    if (levelwidth_tiles <= 0 or levelheight_tiles <= 0 or
        (levelwidth_tiles / 64) > 0xFFFF or (levelheight_tiles / 64) > 0xFFFF):
        raise RuntimeError('invalid map size')
    if ((levelwidth_tiles % 64) != 0) or ((levelheight_tiles % 64) != 0):
        raise RuntimeError('map size in tiles must be multiple of 64')
    GLogger.debug('verified map size')
    
    # Parse properties into a dict of name:unicide -> value:unicode
    levelprops = {}
    ELEMENT_NODE = minidom.Node.ELEMENT_NODE
    for el in el_doc.childNodes:
        if el.nodeType == ELEMENT_NODE and el.tagName == u'properties':
            for el2 in el.childNodes:
                if el2.nodeType != ELEMENT_NODE:
                    continue
                pname = el2.getAttribute(u'name')
                pvalue = el2.getAttribute(u'value')
                levelprops[pname] = pvalue
                
    assert len(levelprops) > 0, 'level properties undefined'
    assert u'Name' in levelprops, 'level name undefined'
    GLogger.debug('loaded properties: %s', ', '.join(levelprops.iterkeys()))
    
    levelinfo = [levelwidth_tiles / 64, levelheight_tiles / 64, levelprops]
    
    # Parse object groups
    # objectgroups: list<[groupname:uni, objects]>
    # objects: list<[name:uni, type:str, pos:(x:int, y:int), size:(width:int, height:int), props]>
    # props: dict<name:uni, value:uni>
    GLogger.debug('reading object groups...')
    els_group = el_doc.getElementsByTagName(u'objectgroup')
    groups = []
    for el_group in els_group:
        gname = el_group.getAttribute(u'name')
        try:
            gindex = int(gname.rsplit(u' ', 1)[-1])
        except ValueError:
            raise RuntimeError('unspecified/unreadable group index for: ' + gname)
        GLogger.debug('reading objects in group %s...', gname)
        # Parse objects
        els_object = el_group.getElementsByTagName(u'object')
        objects = []
        for el_obj in els_object:
            oname = el_obj.getAttribute(u'name')
            otype = str(el_obj.getAttribute(u'type'))   
            opos = (int(el_obj.getAttribute(u'x')),
                    int(el_obj.getAttribute(u'y')))
            osize = (int(el_obj.getAttribute(u'width')),
                     int(el_obj.getAttribute(u'height')))
            
            # Parse optional properties
            els_prop = el_obj.getElementsByTagName(u'property')
            oprops = {}
            for el_prop in els_prop:
                pname = el_prop.getAttribute(u'name')
                pvalue = el_prop.getAttribute(u'value')
                oprops[pname] = pvalue
                
            objects.append([oname, otype, opos, osize, oprops])
            GLogger.debug('read object %s (%s)', oname, otype)
            
        groups.append([gname, gindex, objects])
        GLogger.debug('finished reading group %s with %s objects', gname, len(objects))
        
    GLogger.debug('finished reading object groups')
        
    # Parse tile layers
    GLogger.debug('reading tile layers...')
    els_layer = el_doc.getElementsByTagName(u'layer')
    layers = []
    for el_layer in els_layer:
        layername = el_layer.getAttribute(u'name')
        GLogger.debug('reading layer %s...', layername)
        
        # Try getting index
        try:
            layerindex = int(layername.rsplit(u' ', 1)[-1])
        except ValueError:
            raise RuntimeError('unspecified/unreadable layer index for: ' + layername)
            
        els_data = el_layer.getElementsByTagName(u'data')
        for el_data in els_data:
            assert el_data.getAttribute(u'encoding') == u'base64'
            assert el_data.getAttribute(u'compression') == u'zlib'
            elts_rawtiledata = el_data.childNodes
            assert len(elts_rawtiledata) == 1
            data = elts_rawtiledata[0].data
            data = data.decode('base64')
            data = zlib.decompress(data)
            # Unpack unsigned integers from string
            unpack_from = struct.unpack_from
            tiles = []
            for i in xrange(0, len(data), 4):
                t = unpack_from('I', data, i)
                tiles.append(t[0])
        # name, index, visible, opacity, tiles
        layers.append([layername, layerindex, 0, 0, tiles])

    # Parse tilesets
    GLogger.debug('reading tile sets...')
    els_tileset = el_doc.getElementsByTagName(u'tileset')
    tilesets = []
    if len(els_tileset) == 1: # only one tileset supported for now
        el_tileset = els_tileset[0]
        # Verify and get sizes
        assert el_tileset.getAttribute(u'tilewidth') == u'16'
        assert el_tileset.getAttribute(u'tileheight') == u'16'
        assert el_tileset.getAttribute(u'firstgid') == u'1'
        el_image = el_tileset.getElementsByTagName(u'image')[0]
        tswidth = int(el_image.getAttribute(u'width')) / 16
        tsheight = int(el_image.getAttribute(u'height')) / 16
        
        # Parse tile properies
        props = []
        els_tile = el_tileset.getElementsByTagName(u'tile')
        for el_tile in els_tile:
            tid = int(el_tile.getAttribute(u'id'))
            tpdict = {}
            els_prop = el_tile.getElementsByTagName(u'property')
            for el_prop in els_prop:
                pname = str(el_prop.getAttribute(u'name'))
                pvalue = str(el_prop.getAttribute(u'value'))
                tpdict[pname] = pvalue 
            
            props.append((tid, tpdict))
            
        # name, width, height, images, props
        tilesets.append([None, tswidth, tsheight, None, props])
    else:
        raise RuntimeError('no single tileset found')

    return (levelinfo, tilesets, layers, groups)

def LoadTMX(file, world):
    '''
    Loads a level from a TilEd TMX file.
    '''
    # NOTE: el_* is element object, els_* is list of element objects,
    #       elt_* is text object, elts_* is text object list
    GLogger.info('Loading TMX level file...')
    if isinstance(file, str):
        file = cstringio.StringIO(file)
        
    doc = minidom.parse(file)
    levelinfo, tilesets, layers, groups = _ParseTMX(doc)
    doc.unlink()
    
    # levelinfo: [levelwidth, levelheight, props]
    # groups: list of [name:uni, index:int, objects]
    #   objects: list of [name:uni, type:str, pos:tuple, size:tuple, props]
    # layers: list of [name, index, visible, opacity, tiles]
    # tilesets:list of [name, width, height, images, props]

    if len(tilesets) < 1:
        raise RuntimeError('no tilesets defined')
    elif len(tilesets) > 1:
        raise NotImplementedError('only one tileset supported at the moment')
            
    levelwidth, levelheight, levelprops = levelinfo
    # All tilesets should be the same size
    tswidth = tilesets[0][1]
    tsheight = tilesets[0][2]
        
    # Ensure tile layer 0 exists
    found0 = False
    for layer in layers:
        if layer[1] == 0:
            found0 = True
            break
    if not found0:
        raise RuntimeError('layer 0 undefined')
    GLogger.debug('found tile layer 0')
    
    # Check actor component types
    for group in groups:
        for object in group[2]:
            ctypes = object[1].split(',')
            nctypes = []
            if len(ctypes) < 1:
                raise RuntimeError(u'unspecified type for actor: ' + object[1])
            for ctype in ctypes:
                actype = 'A' + ctype.strip()
                if actype not in GComponentClasses:
                    GLogger.error('invalid component type name: ' + ctype)
                    continue
                nctypes.append(GComponentClasses[actype])
            object[1] = nctypes

    # Encode tiles
    levelsize_tiles = FSizeI(levelinfo[0] * 64, levelinfo[1] * 64)
    default_tile = 16 # Grass tile for now
    pos_zero = FVector2I(0, 0)
    convert = TileFromCoords
    for layer in layers:
        tiles = layer[4]
        for i, tile in enumerate(tiles):
            if tile == 0:
                tiles[i] = default_tile
            else:
                tile -= 1 # Compensate for transparent 0
                tiles[i] = convert(tile % tswidth, tile / tswidth)
        layer[4] = td = FTileData(levelsize_tiles)
        td.SetTilesFast(pos_zero, levelsize_tiles, tiles)
        tiles = None
        
    # Encode tile props
    for tileset in tilesets:
        tileset = tilesets[0]
        propcount = len(tileset[4])
        if propcount > 0:
            newprops = []
            for tid, tprops in tileset[4]:
                tid = convert(tid % tswidth, tid / tswidth)
                newprops.append((tid, tprops))
            tileset[4] = newprops
    
    # Create the level
    tileprops = {
        'Blocking': int,
        'Shape': int
        }
        
    def cvt_vector3f(vstr):
        vstr = vstr.split(',')
        assert len(vstr) == 3
        return FVector3F(float(vstr[0].strip()),
                         float(vstr[1].strip()),
                         float(vstr[2].strip()))
    
    def cvt_bool(vstr):
        return bool(int(vstr))
        
    objectprops = {
        'Transform': {
            'Position': cvt_vector3f,
            },
        'Physics': {
            'CollideActors': cvt_bool,
            'CollideTiles': cvt_bool,
            'CollisionLayer': int
            }
        }
    
    levelname = levelprops[u'Name']
    GLogger.debug('creating level %s...', levelname)
    lev = world.CreateLevel(None, levelname, FSizeI(levelwidth, levelheight))
    try:
        lev.Test(0)
        for layer in layers:
            layerindex = layer[1]
            lev.CreateLayer(layerindex)
            lev.SetTiles(layerindex, pos_zero, layer[4])
            # Apply properties to tiles
            for tid, tprops in tilesets[0][4]:
                try:
                    tinfo = lev.GetTileInfo(tid)
                except IndexError:
                    GLogger.error('tile code not found: %s', tid)
                
                for pname, pvalue in tileprops.iteritems():
                    if pname in tprops:
                        setattr(tinfo, pname, pvalue(tprops[pname]))
                    
        for group in groups:
            gindex = group[1]
            for object in group[2]:
                #   objects: list of [name:uni, type:str, pos:tuple, size:tuple, props]
                act = lev.CreateActor(comps=object[1])
                act.Tag = object[0]
                # Attempt to apply position and size to components if they're defined
                trans = act.Transform
                if trans:
                    px = float(object[2][0]) / 16
                    py = float(object[2][1]) / 16 
                    trans.Position = FVector3F(px, py, 0.0)
                phys = act.Physics
                if phys:
                    sw = float(object[3][0]) / 16
                    sh = float(object[3][1]) / 16
                    phys.Bounds = FRectF(0.0, 0.0, sw, sh)
                # Apply properties
                for pkey, pvalue in object[4].iteritems():
                    # Keys are in format TypeName.AttrName
                    pkeysplit = pkey.split('.')
                    assert len(pkeysplit) == 2
                    ptypename = pkeysplit[0]
                    pname = pkeysplit[1]
                    
                    # Get tree of value converters
                    typetree = objectprops.get(ptypename, None)
                    if typetree is None:
                        continue
                    # Get converter for this attr
                    pvaluecvt = typetree.get(pname, None)
                    if pvaluecvt is None:
                        continue
                    # Get the actual type object
                    ptype = GComponentClasses.get('A' + ptypename, None)
                    if ptype is None:
                        raise RuntimeError('missing type object')
                    comp = act.GetComponent(ptype)
                    if comp is None:
                        continue
                    nvalue = pvaluecvt(pvalue)
                    setattr(comp, pname, nvalue)
                    #print('set attr {1} to {2} on {0}'.format(comp, pname, nvalue))
                

        GLogger.info('Finished TMX level load')
    except Exception:
        world.DestroyLevel(lev)
        raise
    return lev
        
    # INCOMPLETE

def Simulated(func):
    func.Simulated = True
    return func

def ClientMethod(func):
    funcname = func.__name__
    
    @Simulated
    def _ClientMethodWrapper(self, *args):
        world = self.Level.World
        if world.NetIsServer:
            world.NetSendActorRPC(False, self, funcname, *args)
        else:
            return func(self, *args)
   
    return _ClientMethodWrapper

def ServerMethod(func):
    funcname = func.__name__
    
    @Simulated
    def _ServerMethodWrapper(self, *args):
        world = self.Level.World
        if world.NetIsClient:
            world.NetSendActorRPC(False, self, funcname, *args)
        else:
            return func(self, *args)
   
    return _ServerMethodWrapper

def ComponentClass(cls):
    assert issubclass(cls, AComponent)
    name = cls.__name__
    if name in GComponentClasses:
        raise RuntimeError('component class named "{0}" already registered'.format(name))
    GComponentClasses[name] = cls
    return cls

class NoHandler(core.KharliaException):
    pass

@NativeClass
class AComponent(AComponentBase, core.CObject):
    PlaceableClass = False
    
    def __init__(self):
        self.__initnative__()
        core.CObject.__init__(self)
        self._MsgChannel = None
        self._MsgProcTasklet = None
        self._TestTasklet = None
        
    def __getinitargs__(self):
        return tuple()
    
    __getstate_manages_dict__ = True
    def __getstate__(self):
        d = self.__dict__
        del d['_MsgChannel']
        del d['_MsgProcTasklet']
        return (
            self._Owner,
            self._Level,
            self._NoDelete,
            d
            )
    
    def __setstate__(self, state):
        self._Owner = state[0]
        self._Level = state[1]
        self._NoDelete = state[2]
        self.__dict__ = state[3]
        
        assert getattr(self, '_MsgProcTasklet', None) is None
        self.StartMsgProc()
        
        tt = getattr(self, '_TestTasklet', None)
        if tt is not None:
            assert not tt.scheduled
            if tt.restorable:
                # TODO: Restore into level-specific tasker
                tt.insert()
                GLogger.info('tasklet restored: %s', tt)
            else:
                GLogger.error('tasklet not restorable: %s', tt)
    
    def __repr__(self):
        if self.Owner:
            return '<{0} component of {1}>'.format(self.__class__.__name__,
                                                       repr(self.Owner))
        else:
            return '<{0} component>'.format(self.__class__.__name__)
    
    def StartMsgProc(self):
        '''Initialize event channel and event processing tasklet.'''
        self._MsgChannel = stackless.channel()
        self._MsgChannel.preference = -1
        self._MsgProcTasklet = self.Level.ActorTasker.CreateTasklet(self._MsgProcMain)()
        
    def StopMsgProc(self):
        '''Close event channel and kill event processing tasklet.'''
        if self._TestTasklet is not None:
            self._TestTasklet.kill()
            self._TestTasklet = None
        if self._MsgProcTasklet is not None:
            self._MsgProcTasklet.kill()
            self._MsgProcTasklet = None
        if self._MsgChannel is not None:
            self._MsgChannel.close()
            self._MsgChannel = None
                                                   
    def SendMsgArgs(self, name, args):
        '''Send an event to the event channel, providing args as a tuple.'''
        self._MsgChannel.send((name, args))
    
    def SendMsg(self, name, *args):
        '''Send an event to the event channel.'''
        self._MsgChannel.send((name, args))
        
    def _MsgProcMain(self):
        '''Message processing tasklet main.'''
        # TODO: Specify this tasklet as not to be pickled.
        #       If some tasklets can be restarted after an unpickling,
        #       that is safer.
        while 1:
            name, args = self._MsgChannel.receive()
            handler = getattr(self, 'OnRecv' + name, None)
            if not handler:
                GLogger.error('no handler for message %s', name)
            try:
                handler(*args)
            except Exception:
                core.PrintTaskletExc(GLogger.error)
    
@NativeClass
@ComponentClass
class ATransform(ATransformBase, AComponent):
    __repvars__ = ['Position']
    
    PlaceableClass = True
    
    def __init__(self):
        self.__initnative__()
        AComponent.__init__(self)
        
    def __getstate__(self):
        state = (
            super(ATransform, self).__getstate__(),
            self.Position
            )
        return state
    
    def __setstate__(self, state):
        super(ATransform, self).__setstate__(state[0])
        self.Position = state[1]
        
    def __getreplist__(self, role, remoterole):
        if role == ROLE_AUTHORITY:
            yield 'Position',
        

@NativeClass
@ComponentClass
class AMovement(AMovementBase, AComponent):
    __repvars__ = ['Velocity', 'Acceleration', 'MaxVelocity', 'MaxAcceleration']
    PlaceableClass = True
    def __init__(self):
        self.__initnative__()
        AComponent.__init__(self)
        
    def __getstate__(self):
        state = (
            super(AMovement, self).__getstate__(),
            self.Velocity,
            self.Acceleration,
            self.MaxVelocity,
            self.MaxAcceleration,
            self._Transform
            )
        return state
    
    def __setstate__(self, state):
        super(AMovement, self).__setstate__(state[0])
        self.Velocity = state[1]
        self.Acceleration = state[2]
        self.MaxVelocity = state[3]
        self.MaxAcceleration = state[4]
        self._Transform = state[5]
        
    def __getreplist__(self, role, remoterole):
        if role == ROLE_AUTHORITY:
            yield 'Velocity', 'Acceleration', 'MaxVelocity', 'MaxAcceleration'
    
@NativeClass
@ComponentClass
class ARender(ARenderBase, AComponent):
    __repvars__ = ['RenderType', 'Direction', 'Hidden']
    PlaceableClass = True
    def __init__(self):
        self.__initnative__()
        AComponent.__init__(self)
        
    def __getstate__(self):
        state = (
            super(ARender, self).__getstate__(),
            self.RenderType,
            self.Direction,
            self.Hidden,
            self._Transform
        )
        return state
    
    def __setstate__(self, state):
        super(ARender, self).__setstate__(state[0])
        self.RenderType = state[1]
        self.Direction = state[2]
        self.Hidden = state[3]
        self._Transform = state[4]
        
    def __getreplist__(self, role, remoterole):
        if role == ROLE_AUTHORITY:
            yield 'RenderType', 'Direction', 'Hidden'
    
@NativeClass
@ComponentClass
class APhysics(APhysicsBase, AComponent):
    __repvars__ = ['Bounds', 'CollideActors', 'CollideTiles', 'CollisionLayer']
    PlaceableClass = True
    def __init__(self):
        self.__initnative__()
        AComponent.__init__(self)
        
    def __getstate__(self):
        state = (
            super(APhysics, self).__getstate__(),
            self.Bounds,
            self.CollideActors,
            self.CollideTiles,
            self.CollisionLayer,
            self._Transform
        )
        return state
    
    def __setstate__(self, state):
        super(APhysics, self).__setstate__(state[0])
        self.Bounds = state[1]
        self.CollideActors = state[2]
        self.CollideTiles = state[3]
        self.CollisionLayer = state[4]
        self._Transform = state[5]
        
    def __getreplist__(self, role, remoterole):
        if role == ROLE_AUTHORITY:
            yield 'Bounds', 'CollideActors', 'CollideTiles', 'CollisionLayer'
        
    def OnRecvTouched(self, other):
        GLogger.debug('%s touched by %s', self, other)
        
    def SendTouched(self, other):
        self.SendMsgArgs('Touched', (other,))
    
@NativeClass
@ComponentClass
class ACamera(ACameraBase, AComponent):
    __repvars__ = ['Target', 'TargetOffset']
    PlaceableClass = True
    def __init__(self):
        self.__initnative__()
        AComponent.__init__(self)
        
    def __getstate__(self):
        state = (
            super(ACamera, self).__getstate__(),
            self.Target,
            self.TargetOffset
            )
        return state
    
    def __setstate__(self, state):
        super(ACamera, self).__setstate__(state[0])
        self.Target = state[1]
        self.TargetOffset = state[2]
        
    def __getreplist__(self, role, remoterole):
        if role == ROLE_AUTHORITY:
            yield 'Target', 'TargetOffset'
    
@NativeClass
class AController(AControllerBase, AComponent):
    __repvars__ = ['Pawn']
    def __init__(self):
        self.__initnative__()
        AComponent.__init__(self)
        
    def __getstate__(self):
        return (
            super(AController, self).__getstate__(),
            self.Pawn
            )
    
    def __setstate__(self, state):
        super(AController, self).__setstate__(state[0])
        self.Pawn = state[1]
        
    def __getreplist__(self, role, remoterole):
        if role == ROLE_AUTHORITY:
            yield 'Pawn', 
        
    def OnDestroyed(self):
        self.Unpossess()
        super(AController, self).OnDestroyed()
        
    def Possess(self, pawn):
        if not isinstance(pawn, APawn):
            raise TypeError('not APawn')
        pawn.OnPossessed(self)
        self._Pawn = pawn
        
    def Unpossess(self):
        if self._Pawn is not None:
            self._Pawn.OnUnpossessed()
            self._Pawn = None
    
@NativeClass
@ComponentClass
class APlayerController(APlayerControllerBase, AController):
    __repvars__ = ['Camera']
    PlaceableClass = True
    def __init__(self):
        self.__initnative__()
        AController.__init__(self)
        self._Game = None
        
    def __getstate__(self):
        return (
            super(APlayerController, self).__getstate__(),
            None, # Player
            self.Camera
            )
    
    def __setstate__(self, state):
        super(APlayerController, self).__setstate__(state[0])
        #self.Player = state[1]
        self.Camera = state[2]
        
    def __getreplist__(self, role, remoterole):
        if role == ROLE_AUTHORITY:
            yield 'Camera',
        
    def Init(self, owner):
        super(APlayerController, self).Init(owner)
        
    def Exit(self):
        #if self._Game:
        #    self._Game.OnLogout(self)
        #    self._Game = None
        super(APlayerController, self).Exit()
        
    def OnRecvGameStarted(self, game):
        assert not self._Game
        self._Game = game
        
    def OnRecvGameStopped(self):
        assert self._Game
        self._Game.OnLogout(self)
    
@NativeClass
@ComponentClass
class APawn(APawnBase, AComponent):
    PlaceableClass = True
    def __init__(self):
        self.__initnative__()
        AComponent.__init__(self)
        
    def __getstate__(self):
        return (
            super(APawn, self).__getstate__(),
            self._Controller
            )
    
    def __setstate__(self, state):
        super(APawn, self).__setstate__(state[0])
        self._Controller = state[1]
        
    def OnPossessed(self, controller):
        self._Controller = controller
        
        #if isinstance(controller, APlayerController):
        #    if self.Level.World.NetMode != NM_STANDALONE:
        #        self.RemoteRole = ROLE_AUTO_PROXY
        
    def OnUnpossessed(self):
        self._Controller = None
        
class FRepClassInfo(object):
    __slots__ = [
        'Class', # Component class
        'Index', # class.__repindex__
        'ClassIndices', # class.__repclassindices__
        'Vars', # class.__allrepvars__
        'Map', # dict of index -> varname and varname -> index
        'Recent', # recent values, len(Vars)
        'Inst' # component instance
    ]
        
@NativeClass
@ComponentClass
class ANetwork(ANetworkBase, AComponent):
    PlaceableClass = False
    def __init__(self):
        self.__initnative__()
        AComponent.__init__(self)
        #self.VarsList = None
        # map index -> varname, and varname -> index
        #self.VarsDict = None
        #self.ClassList = None
        # map varname -> class
        #self.ClassDict = None
        # repindex -> repclassinfo
        self.RepClassMap = None
        self.RepClasses = None
        self.RecentPython = None
        self.PickleFunc = cpickle.dumps
        self.PickleProtocol = cpickle.HIGHEST_PROTOCOL
        
    def InitPythonRep(self):
        '''
        Initialize the metadata needed for Python replication.
        
        This method will iterate through the components of its owner and
        create a FRepClassInfo instance for each component that can
        replicate variables.
        
        self.HasPythonRep is True if initialization was successful.
        '''
        assert self.Owner
        assert not self.HasPythonRep
        
        GLogger.debug('initing python replication for %s ...', self.Owner)
        
        classes = []
        clsmap = {}
        # Iterate through the components in owner.
        for comp in self.Owner:
            assert isinstance(comp, AComponent)
            compcls = type(comp)
            # Component class will have __repindex__ if it has
            # at least one replicated variable
            index = getattr(compcls, '__repindex__', None)
            if index is None:
                continue
            vars = compcls.__allrepvars__
            assert len(vars) > 0
            rci = FRepClassInfo()
            rci.Class = compcls
            rci.Index = index
            rci.Vars = vars
            rci.ClassIndices = compcls.__repclassindices__
            rci.Inst = comp
            clsmap[rci.Index] = rci
            classes.append(rci)
            GLogger.debug('found component %s:', rci.Class.__name__)
            GLogger.debug('  index = %s', rci.Index)
            GLogger.debug('  vars = %s', rci.Vars)
            GLogger.debug('  classindices = %s', rci.ClassIndices)
        
        if len(classes) > 0:
            classes.sort(lambda x, y: cmp(x.Index, y.Index))
            for rci in classes:
                rci.Map = map = {}
                for index, varname in enumerate(rci.Vars):
                    map[varname] = index
                    map[index] = varname
                rci.Recent = [None] * len(rci.Vars)
            self.RepClasses = classes
            self.RepClassMap = clsmap
            self._HasPythonRep = True
        else:
            self._HasPythonRep = False
        
    def ExitPythonRep(self):
        assert self.HasPythonRep
        
        self.RecentPython = None
        self.RepClasses = None
        self.RepClassMap = None
        self._HasPythonRep = False
        
    def ReplicatePython(self, all=False):
        '''
        Replicate Python variables.
        
        This method will iteratate through each FRepClassInfo in
        self.RepClasses and perform replication for each component.
        
        Replication involves calling __getreplist__ to get variable names
        that are candidates for replication, then comparing them to the
        most recently replicated value. If 'all' is True then
        __getreplist__ will not be called and all variables will be tested.
        '''
        assert self.HasPythonRep
        
        out = []
        out_append = out.append
        role = self.Role
        remoterole = self.RemoteRole
        for rci in self.RepClasses:
            #inst = self.Owner.GetComponent(rci.Class)
            inst = rci.Inst
            #assert inst
            mro = rci.Class.mro()
            # The function __getreplist__ is called for each class in
            # the mro. It will yield tuples of variable names that
            # are going to be replicated.
            if all:
                varnames = rci.Vars
            else:
                varnames = []
                for index in rci.ClassIndices:
                    for reptuple in mro[index].__getreplist__(inst, role, remoterole):
                        varnames.extend(reptuple)
            
            cout = []
            cout_append = cout.append
            map = rci.Map
            recent = rci.Recent
            for varname in varnames:
                index = map[varname]
                value = getattr(inst, varname)
                # consider using 'is not' here
                if value != recent[index]:
                    cout_append(index)
                    cout_append(value)
                    recent[index] = value
                    
            if len(cout) > 0:
                out_append(rci.Index)
                out_append(cout)
        
        if len(out) == 0:
            return None
        #return self.PickleDumpImpl(out)
        return out
        
    def UpdatePython(self):
        pass
        
    def PickleDumpImpl(self, data):
        return self.PickleFunc(data, self.PickleProtocol)

@NativeClass
class CActor(CActorBase, core.CObject):
    def __init__(self):
        self.__initnative__()
        core.CObject.__init__(self)
        self.Transient = False
    
    def GetIDStr(self):
        if self.Tag:
            # TODO: If self.ID is not converter into string, exception
            #       causes assertion with Python garbage collector.
            #       Investigate this sometime.
            return ''.join((str(self.ID), ':', str(self.Tag)))
        else:
            return str(self.ID)
        
    def __repr__(self):
        return "<{0} {1}>".format(self.__class__.__name__, self.GetIDStr())

    def __reduce_ex__(self, protocol):
        assert protocol == 2
        return (CActor, (), self.__getstate__())

    def OnCreated(self):
        GLogger.debug('created actor ' + repr(self))
        super(CActor, self).OnCreated()
        
    def OnDestroyed(self):
        GLogger.debug('destroyed actor ' + repr(self))
        super(CActor, self).OnDestroyed()
        
    def OnGameStarted(self, game):
        super(CActor, self).OnGameStarted(game)
        GLogger.debug('started game for ' + repr(self))
    
    def OnGameStopped(self):
        super(CActor, self).OnGameStopped()
        GLogger.debug('stopped game for ' + repr(self))
        
    @staticmethod
    def Pickle(actor, pickler, prot):
        assert GActorPickleState is None
        return pickler(actor, prot)
    
    #@staticmethod
    #def _UnpickleActor(owner, name):
    #    if GActorPickleState is not None:
    #        level, newname = GActorPickleState['level'], GActorPickleState['newname']
    #        if newname is None:
    #            return level.CreateActor(None, owner, name)
    #        else:
    #            return level.CreateActor(None, owner, newname)
    #    elif GUnpicklingLevel is not None:
    #        return GUnpicklingLevel.CreateActor(None, owner, name)
    #    else:
    #        raise RuntimeError('invalid unpickling')
    
    @staticmethod
    def Unpickle(data, level, unpickler, name=None):
        global GActorPickleState
        if GActorPickleState is not None:
            raise RuntimeError('actor unpickling already in progress')
        GActorPickleState = {
            'level': level,
            'newname': name
            }
        act = unpickler(data)
        GActorPickleState = None
        return act

#copy_reg.constructor(CActor._UnpickleActor)

#@NativeClass
#class AActor(_engine.AActorBase, core.CObject):
#    '''The base class for all objects that exist within a level.
#    
#    Native variables:
#    
#        Name - str - The name of the actor, always valid
#        Level - CLevel - The level (CLevel) this actor is within
#        Owner - AActor - The owning actor if one exists, None otherwise
#        Direction - EDirection - The direction the sprite is pointing in
#        Hidden - bool -
#        Position - FVector3F -
#        Velocity - FVector3F -
#        Acceleration - FVector3F -
#        MaxAccelRate - FVector3F -
#        Role - ENetRole
#        RemoteRole - ENetRole
#        NetInitial - bool
#        NetDirty - bool
#        Transient - bool - True if the actor should not be serialized
#
#    '''
#    __getstate_manages_dict__ = True
#    NetVars = ['TestVar']
#    PlaceableClass = True
#
#    def __init__(self):
#        self.__initnative__()
#        core.CObject.__init__(self)
#        self.NetID = None
#        self.TestVar = 'test'
#        self.Tasklet = None
#        self.Transient = False
#    
#    def __repr__(self):
#        if self.Name is not None:
#            return '<{1} \'{0}\'>'.format(self.Name,
#                                            type(self).__name__)
#        return super(AActor, self).__repr__()
#
#    def __getstate__(self):
#        out = [
#            self.__dict__,
#            self.Dir,
#            self.Hidden,
#            self.Velocity,
#            self.NetInitial,
#            self.NetDirty
#            ]
#        return out
#
#    def __setstate__(self, s):
#        self.__dict__.update(s[0])
#        self.Dir = s[1]
#        self.Hidden = s[2]
#        self.Velocity = s[3]
#        self.NetInitial = s[4]
#        self.NetDirty = s[5]
#
#    def __reduce_ex__(self, protocol):
#        if protocol != 2:
#            raise NotImplementedError()
#        #ActorClass, ActorName, ActorPos, ActorOwner
#        return (_UnpickleActor, (
#                    type(self),
#                    self.Name,
#                    self.Position,
#                    self.Owner
#                ),
#                self.__getstate__())
#
#    def GetRepList(self):
#        if self.NetInitial and self.Role == ROLE_AUTHORITY:
#            yield 'TestVar',
#
#    #def StartTasklet(self):
#    #   if self.Tasklet is not None:
#    #        raise RuntimeError('tasklet already started')
#    #    if not hasattr(self, 'Tick'):
#    #        return
#    #    self.Tasklet = GApp.CreateTasklet(self.TaskletMain)()
#    
#    #def StopTasklet(self):
#    #    if self.Tasklet is not None:
#    #        self.Tasklet.kill()
#    #        self.Tasklet = None
#
#    #def TaskletMain(self):
#    #    yield_ = GApp.Yield
#    #    while True:
#    #        self.Tick()
#    #        yield_()
#    
#    @Simulated
#    def DisplayRPCMessage(self, msg):
#        print('GOT MESSAGE BY RPC:', msg)
#        
#    @ClientMethod
#    def ClientDisplayRPCMessage(self, msg):
#        print('GOT MESSAGE BY CLIENTMETHOD RPC:', msg)
#
#    @ServerMethod
#    def ServerDisplayRPCMessage(self, msg):
#        print('GOT MESSAGE BY SERVERMETHOD RPC:', msg)
#
#    @Simulated
#    def SimTest(self):
#        print('THIS FUNCTION IS SIMULATED')
#        
#    def SimTestN(self):
#        print('THIS FUNCTION IS NOT SIMULATED')
#
#    def OnCreated(self):
#        GLogger.info('created actor ' + repr(self))
#        super(AActor, self).OnCreated()
#        
#    def OnDestroyed(self):
#        GLogger.info('destroyed actor ' + repr(self))
#        super(AActor, self).OnDestroyed()
#        
#    def OnGainedChild(self, other):
#        super(AActor, self).OnGainedChild(other)
#        GLogger.debug('actor %s gained child %s', self, other)
#        
#    def OnLostChild(self, other):
#        super(AActor, self).OnLostChild(other)
#        GLogger.debug('actor %s lost child %s', self, other)
#
#AActor = ActorClass(AActor)

GPickledTypes = None
GPickledTypeList = None
GPickleLevel = None
GTraceInst = None

@NativeClass
class CLevel(CLevelBase, core.CObject):
    # These are the first bytes of a proper pickled level, used as first-step
    # verification of what we're unpickling
    LEVEL_PICKLE_HEADER = '\x80\x02ckharlia.engine\n_UnpickleLevel\n'
    # Dict of globals that can be safely unpickled from levels
    SAFE_UNPICKLE_GLOBALS = {
        '__builtin__': [
            'getattr',
            '__import__'
            ],
        'stackless._wrap': True,
        'uuid': ['UUID'],
        'kharlia.__core': True,
        'kharlia._core': True,
        'kharlia.core': True,
        'kharlia.__engine': True,
        'kharlia._engine': True,
        'kharlia.engine': True,
        }

    def __init__(self):
        self.__initnative__()
        core.CObject.__init__(self)
        
    def IterTileGroups(self):
        for i in xrange(self.TileGroupCount):
            yield self.GetTileGroup(i)

    def IterLayerInfos(self):
        for i in self.LayerInfoIndices:
            yield i, self.GetLayerInfo(i)

    def IterActors(self, comptype=None):
        if comptype is None:
            get = self.GetActorByIndex
            for index in xrange(self.ActorCount):
                yield get(index)
        elif issubclass(comptype, AComponent):
            get = self.GetActorByIndex
            for index in xrange(self.ActorCount):
                comp = get(index).GetComponent(comptype)
                if comp is None:
                    continue
                yield comp
        else:
            raise TypeError('comptype must be None or subclass of AComponent')

    def __reduce_ex__(self, protocol):
        assert protocol == 2
        return (_UnpickleLevel, (self.GUID, self.Name, self.Size), self.__getstate__())
        
    def ClearActors(self):
        destroy = self.DestroyActor
        get = self.GetActorByIndex
        for index in reversed(range(self.ActorCount)):
            destroy(get(index))
    
    def OnCreated(self):
        super(CLevel, self).OnCreated()
        t = self.ActorTasker.CreateTasklet(self.ActorTickerMain)()
        self.ActorTasker.InsertTasklet(t, self.TICK_GROUP)
        
    def OnDestroyed(self):
        super(CLevel, self).OnDestroyed()

    @staticmethod
    def SavePackage(level, file):
        if not isinstance(level, CLevel):
            raise TypeError()
        GLogger.info('Saving package for level: %s', level.Name)
        data = CLevel.Pickle(level)
        GLogger.info('Pickled data, now saving to file...')
        zipf = zipfile.ZipFile(file, 'w', zipfile.ZIP_DEFLATED)
        zipf.writestr('__level__.pickle', data)
        zipf.close()
        GLogger.info('Finished saving package')

    @staticmethod
    def LoadPackage(world, file, name=None):
        GLogger.info('Loading level package...')
        zipf = zipfile.ZipFile(file, 'r')
        data = zipf.read('__level__.pickle')
        zipf.close()
        GLogger.info('Read level data from file, now unpickling...')
        lev = CLevel.Unpickle(world, data, name)
        GLogger.info('Unpickling finished')
        return lev

    @staticmethod
    def _PersistentID(obj):
        objtype = type(obj)
        
        # Debug stuff
        obs = str(obj)
        if len(obs) > 64:
            obs = obs[:64]
        GPickledTypeList.append((unicode(objtype), obs))
        GPickledTypes[objtype] = True
        
        if obj is None:
            return None
        elif GTraceInst and obj == GTraceInst:
            return 'D'
        elif objtype is CActor and obj.Level is not GPickleLevel:
            #raise NotImplementedError('far actor refs')
            return 'F:{0}:{1}'.format(obj.Level.Name, obj.ID)
        else:
            return None

    @staticmethod
    def _PersistentLoad(id):
        if id == '0':
            return None
        elif id == 'D':
            return GTraceInst
        elif id.startswith('F'):
            # TODO: Decide how to handle refs we can't unpickle if not
            #       simply setting them to None
            tokens = id.split(':')
            assert len(tokens) == 3
            levelname = unicode(tokens[1])
            actid = int(tokens[2])
            
            lev = GUnpicklingWorld.GetLevel(levelname)
            if not lev:
                return None
            return lev.GetActorByID(actid)
        else:
            raise NotImplementedError()

    @staticmethod
    def Pickle(level, file=None):
        global GPickledTypes, GPickledTypeList, GTraceInst, GPickleLevel
        if file is None:
            file = cstringio.StringIO()
            isstr = True
        else:
            isstr = False
        pickler = cpickle.Pickler(file, cpickle.HIGHEST_PROTOCOL)
        pickler.persistent_id = CLevel._PersistentID
        GPickleLevel = level
        GPickledTypes = {}
        GPickledTypeList = []
        GTraceInst = sys.gettrace()
        if GTraceInst:
            assert GTraceInst is core.TraceDispatchWrapper
        try:
            pickler.dump(level)
        finally:
            # Debug info
            if 0:
                #for cls in GPickledTypes.iterkeys():
                #    print(cls)
                dfile = io.open('pickledtypes.txt', 'w')
                for typename, objstr in GPickledTypeList:
                    try:
                        dfile.write(u'{0} - {1}\n'.format(typename, objstr.encode('string_escape')))
                    except Exception:
                        dfile.write(u'{0} - (fail)\n'.format(typename))
                dfile.close()
            GPickleLevel = None
            GPickledTypes = None
            GPickledTypeList = None
            GTraceInst = None
        if isstr:
            return file.getvalue()

    @staticmethod
    def _FindGlobal(module, cls):
        GLogger.debug('finding global: %s %s', module, cls)
        # No restricting yet
        if 0:
            nlist = CLevel.SAFE_UNPICKLE_GLOBALS.get(module, None)
            if nlist is None:
                raise RuntimeError('unpickling module %s not allowed', module)
            if nlist is not True:
                if cls not in nlist:
                    raise RuntimeError('unpickling attr %s of module %s not allowed', cls, module)
        mod = __import__(module, fromlist=[cls], level=0)
        return getattr(mod, cls)

    @staticmethod
    def Unpickle(world, pdata, name=None):
        global GUnpicklingWorld, GUnpicklingLevel, GUnpicklingLevelName, GTraceInst

        if not isinstance(world, CWorld):
            raise TypeError('world must be instance of CWorld')
        if name is not None and not isinstance(name, basestring):
            raise TypeError('name must be instance of basestring')
        if GUnpicklingWorld is not None:
            raise RuntimeError('unpickling already in progress')
            
        GUnpicklingWorld = world
        GUnpicklingLevel = None
        GUnpicklingLevelName = name
        GTraceInst = sys.gettrace()
        if GTraceInst:
            assert GTraceInst is core.TraceDispatchWrapper
        try:
            if isinstance(pdata, str):
                if not pdata.startswith(CLevel.LEVEL_PICKLE_HEADER):
                    raise RuntimeError('pickle file is malformed or does not contain a level')
                unpickler = cpickle.Unpickler(cstringio.StringIO(pdata))
                unpickler.find_global = CLevel._FindGlobal
                unpickler.persistent_load = CLevel._PersistentLoad
                lev = unpickler.load()
            else:
                pos = pdata.tell()
                head = pdata.read(len(CLevel.LEVEL_PICKLE_HEADER))
                pdata.seek(pos)
                if head != CLevel.LEVEL_PICKLE_HEADER:
                    raise RuntimeError('pickle file is malformed or does not contain a level')
                unpickler = cpickle.Unpickler(pdata)
                unpickler.find_global = CLevel._FindGlobal
                unpickler.persistent_load = CLevel._PersistentLoad
                lev = unpickler.load()
        except Exception:
            if GUnpicklingLevel is not None:
                GUnpicklingWorld.DestroyLevel(GUnpicklingLevel)
            raise
        finally:
            GUnpicklingWorld = None
            GUnpicklingLevel = None
            GUnpicklingLevelName = None
            GTraceInst = None
        return lev

def _UnpickleLevel(guid, name, size):
    '''
    Used to create level instance when unpickling
    '''
    global GUnpicklingLevel
    if GUnpicklingLevelName is not None:
        name = GUnpicklingLevelName
    lev = GUnpicklingWorld.CreateLevel(guid, name, size)
    GUnpicklingLevel = lev
    return lev

copy_reg.constructor(_UnpickleLevel)

@NativeClass
class CPlayer(CPlayerBase, core.CObject):
    def __init__(self, isLocal=False):
        self.__initnative__(None, isLocal)
        core.CObject.__init__(self)

@NativeClass
class CGame(CGameBase, core.CObject):
    def __init__(self):
        self.__initnative__()
        core.CObject.__init__(self)
        self.PlayerControllerClass = APlayerController
        self.StartPosition = None
        self.NumPlayers = 0
        
    def OnStartGame(self, world):
        super(CGame, self).OnStartGame(world)
        assert self.World.Game is self
        
    def OnStopGame(self):
        super(CGame, self).OnStopGame()
        
    def OnLogin(self):
        assert issubclass(self.PlayerControllerClass, APlayerController)
        level = self.World.DefaultLevel
        assert level
        pcact = level.CreateActor(self.PlayerControllerClass)
        pc = pcact.PlayerController
        assert pc
        self.NumPlayers += 1
        GLogger.info('player login: %s', pc)
        return pc
    
    def OnLogout(self, c):
        if isinstance(c, APlayerController):
            GLogger.info('player logout: %s', c)
            self.NumPlayers -= 1

@NativeClass
class CWorld(CWorldBase, core.CObject):
    def __init__(self, path=None):
        self.__initnative__()
        core.CObject.__init__(self)
        self.Path = path
        self.IsTransient = True if path is None else False
        #self.Controllers = weakref.WeakSet()

    def _DestroyLevels(self):
        levels = []
        for level in self.Levels.itervalues():
            levels.append(level)
        for level in levels:
            self.DestroyLevel(level)
        
    def NetSendActorRPC(self, reliable, actor, funcname, *args):
        super(CWorld, self).NetSendActorRPC(reliable, actor, funcname, args)

    def CreatePlayerActor(self, player, remoterole=ROLE_NONE):
        if not player.IsLocal:
            raise NotImplementedError()
        if not self.Game:
            raise RuntimeError('no game set')
        try:
            pc = self.Game.OnLogin()
        except Exception as ex:
            GLogger.error('Login failed: %s', ex)
            raise
        pc.Player = player
        player.Controller = pc
        #self.Controllers.add(pc)
        return pc

@NativeClass
class CClient(CClientBase, core.CObject):
    def __init__(self):
        self.__initnative__()
        core.CObject.__init__(self)

@NativeClass
class CEngine(CEngineBase, core.CSubsystem):
    def __init__(self):
        self.__initnative__()
        core.CSubsystem.__init__(self)
        self.TestActor = None
        self.TestMovement = None

    @property
    def EditorSelection(self):
        return self._EditorSelection
    
    @EditorSelection.setter
    def EditorSelection(self, value):
        if value is None:
            self._EditorSelection = FRectF()
        else:
            self._EditorSelection = value

    def Init(self):
        InitComponentClasses()
        CWorld.SetLevelClass(CLevel)
        CLevel.SetPlayerControllerClass(APlayerController)
        CLevel.SetActorClass(CActor)
        self.Config = core.CApp.GetInstance().ConfigRoot['Engine']['Engine']
        super(CEngine, self).Init()

    def ReportTickTime(self, time):
        GLogger.debug('reporting tick time: %s', time)

    def Exit(self):
        self.TestActor = None
        self.TestMovement = None
        super(CEngine, self).Exit()

class CEngineService(core.CService):
    '''
    Manages the initialization and linking of the engine singleton
    '''
    Name = 'Game Engine Service'
    Depends = []
    
    def __init__(self, *args, **kwds):
        core.CService.__init__(self, *args, **kwds)
        self.Engine = None
    
    @property
    def IsStarted(self):
        return self._IsStarted and self.Engine
    
    def OnStart(self):
        global GLogger

        GLogger = logging.getLogger('engine')

        app = core.CApp.GetInstance()
        cfg = app.ConfigRoot['Engine']['Engine']

        clsname = cfg.get('ClassName', 'kharlia.engine.CEngine')
        ssindex = cfg.get('SubsystemIndex', 10)
        cls = core.ResolveName(clsname)
        if not issubclass(cls, CEngine):
            raise TypeError('Engine class must be CEngine or a subclass')

        eng = cls()
        eng.Init()
        app.AddSubsystem(ssindex, eng)
        self.Engine = eng

        GLogger.debug('initialized the engine')
    
    def OnStop(self):
        global GLogger
        
        core.CApp.GetInstance().RemoveSubsystem(self.Engine)
        self.Engine.Exit()
        self.Engine = None
        GLogger.debug('exited the engine')
        GLogger = None
        
    def InitEngine(self):
        pass

#@NativeClass
#class CActorReplicator(_engine.CActorReplicatorBase, core.CObject):
#    '''
#    An object that binds to an actor and handles calculation of variables that
#    need to be replicated.
#    '''
#    def __init__(self):
#        self.__initnative__()
#        core.CObject.__init__(self)
#        self.PickleFunc = cpickle.dumps
#        self.PickleProtocol = cpickle.HIGHEST_PROTOCOL
#
#    def BindActor(self, actor):
#        if actor is not None:
#            if not isinstance(actor, AActor):
#                raise TypeError('not an actor')
#            if not hasattr(type(actor), '_AllNetVars'):
#                raise RuntimeError('Actor should have some net vars')
#            self.ActorClass = aclass = type(actor)
#            self.NetVarsList = netvars = aclass._AllNetVars
#            super(CActorReplicator, self).BindActor(actor)
#            # Create dict for converting indicies to names and vice versa
#            nvd = {}
#            for i, varname in enumerate(self.NetVarsList):
#                nvd[i] = varname
#                nvd[varname] = i
#            self.NetVarsDict = nvd
#            self.RecentPython = [None] * len(self.NetVarsList)
#            self.Initial = True
#            GRepLogger.debug('inited actor replicator for: %s', actor)
#            GRepLogger.debug('net var list: %s', self.NetVarsList)
#        else:
#            self.UnbindActor()
#
#    def UpdatePython(self, datastr):
#        actor = self.Actor
#        if actor is None:
#            raise RuntimeError('no actor bound')
#        changes = cpickle.loads(datastr)
#        print('updating actor with changes:', changes)
#        nvdict = self.NetVarsDict
#        for i in xrange(0, len(changes), 2):
#            setattr(actor, nvdict[changes[i]], changes[i + 1])